前幾日的文章,已經利用scheduler訓練出在Validation上還不錯的結果,今天就來test一下,順帶討論一下test的一些哲學吧!
一般在我們進行訓練模型的任務時,大致上就是分成Train、Validation以及Test的三個階段。讓我們回顧一下,其各自的角色分別是
在我們前面已經介紹、並建構用來做實驗的Lightning-Module之中,除了我們先前用過train_step
跟validation_step
功能以外,自然也有相應的test_step
。
具體只要一樣在module的class內加上:
def test_step(self, batch: Any, batch_idx: int):
inputs, preds, labels, loss = self.step(batch)
self.log('test/loss', loss.item(), on_step=False, on_epoch=True, batch_size = inputs.shape[0])
return {
'loss' : loss,
'preds' : preds,
'labels' : labels
}
接著一樣加上對應的epoch_end
,就可以計算metrics了:
def test_epoch_end(self, validation_step_outputs: List[Any]):
preds = torch.cat([output['preds'] for output in validation_step_outputs], dim=0).float()
labels = torch.cat([output['labels'] for output in validation_step_outputs], dim=0).long()
probs = torch.nn.Sigmoid()(preds)
# compute metrics and log
acc_score = torchmetrics.functional.accuracy(probs, labels, mdmc_average = 'global')
auc_score = monai.metrics.compute_roc_auc(probs, labels, average='macro')
self.log('test/acc', acc_score.item())
self.log('test/auroc', auc_score.item())
之後重新build model的物件,把訓練好的權重讀進來,使用lightning-module對應的test函式,就可以進行test set的計算了:
# build model and load trained model
net = model.MultiLabelsModel(CONFIG)
net = net.load_from_checkpoint(CONFIG['evaluate']['weights_path'])
# initialize the Trainer
trainer = pl.Trainer(**CONFIG['evaluate']['tester'])
# test the model
trainer.test(net, dataloaders=data_generator)
具體可以參考 evaluation.py,一樣可以用
# python src/evaluate.py --config=hparams.yaml
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
IPU available: False, using: 0 IPUs
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Testing DataLoader 0: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:06<00:00, 6.89it/s]
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Test metric ┃ DataLoader 0 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ test/acc │ 0.9475677013397217 │
│ test/auroc │ 0.7314980626106262 │
│ test/loss │ 0.16936460137367249 │
└───────────────────────────┴───────────────────────────┘
結果跟驗證集的結果十分的接近,算是一個相當不錯的結果。
另外注意一下是,我這裡test時的batch size = 512,純粹就是比較快。而且正常情況下test時不論batch size多少都不應該影響計算結果。覺得結果怪怪時,不妨可以試試看,如果不同的話可能就是程式出現了什麼問題。
雖然這樣下小標題可能誇張了!但其實我認為在實務上,Test Set其實隱藏著很多的巧思。
首先是第一種常見的狀況就是,開發者過多且反覆的對Test Set計算結果。這個情況在各種資料的比賽跟以準確度為驗收標準的專案上尤其常見,大部分都會演變成Test Set儼然已經變成另外一個Validation set,在資料有限的狀況下,很難知道準確度的上升到底只是hyperparameter fit on test set還是真的找到了什麼insignt。有些團隊則會利用類似Cross-validation的方式來驗證模型的好壞以避免此情形,也不失一個好方法。
另一種狀況則是更根本的問題,被挑選的Test set其究竟代表什麼?是否與訓練樣本具有相同的分佈?是否與未來真實要處理的問題有相同分佈?種種的問題。分佈問題這一點在像是ChestMNIST資料量很龐大的資料集通常不是大問題,畢竟量大,抽樣基本上誤差也不會大到哪裡去,但小樣本就必須要很注意這東西。尤其是label的分佈,會直接影響到模型對label precition的偏好,不可不慎。
還有就是一些實務上會遇到的準確度下降的問題,這邊簡稱是各種Drifting,這個會在最後一天作為收尾來介紹。